#!/bin/sh
usage () { echo Usage: `basename $0` '[-h|-u] [-r ref] [--] command...'; }
#############################################################################
##
## Copyright (C) 2016 The Qt Company Ltd.
## Contact: https://www.qt.io/licensing/
##
## This file is part of the release tools of the Qt Toolkit.
##
## $QT_BEGIN_LICENSE:LGPL$
## Commercial License Usage
## Licensees holding valid commercial Qt licenses may use this file in
## accordance with the commercial license agreement provided with the
## Software or, alternatively, in accordance with the terms contained in
## a written agreement between you and The Qt Company. For licensing terms
## and conditions see https://www.qt.io/terms-conditions. For further
## information use the contact form at https://www.qt.io/contact-us.
##
## GNU Lesser General Public License Usage
## Alternatively, this file may be used under the terms of the GNU Lesser
## General Public License version 3 as published by the Free Software
## Foundation and appearing in the file LICENSE.LGPL3 included in the
## packaging of this file. Please review the following information to
## ensure the GNU Lesser General Public License version 3 requirements
## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
##
## GNU General Public License Usage
## Alternatively, this file may be used under the terms of the GNU
## General Public License version 2.0 or (at your option) the GNU General
## Public license version 3 or any later version approved by the KDE Free
## Qt Foundation. The licenses are as published by the Free Software
## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
## included in the packaging of this file. Please review the following
## information to ensure the GNU General Public License requirements will
## be met: https://www.gnu.org/licenses/gpl-2.0.html and
## https://www.gnu.org/licenses/gpl-3.0.html.
##
## $QT_END_LICENSE$
##
#############################################################################

help () {
    usage
    # Example data:
    [ "$LAST" ] || LAST=v5.9.0
    [ "$NEXT" ] || NEXT=5.10
    [ "$TASK" ] || TASK=QTBUG-5678
    ME=`basename $0`
    cat <<EOF

Run from the top-level (qt5) directory of a full Qt checkout with the
module-set of an imminent release.  For API change review generation,
check out .gitmodules on the reference version, so as to use its list
of modules (and skip modules that were in preview back then).  Give
this script a command to run in each module; the purpose of this
script is to run api-review-gen on each; but it also serves to prepare
for this and later to run a git push to send the results to Gerrit for
review.  See examples below and output of ./api-review-gen -h for
further details.

Although this script can be used to run a general command in each
module, its selection of modules is specifically geared towards the
particular task of generating API change reviews, under the conditions
described above; a general module iterator might one day replace it,
but please don't try to evolve this into that.

Options:

 -u
 --usage   Print simple usage summary (first line above) and exit.

 -h
 --help    Print this help and exit.

 -r ref
 --require ref
           Limit to modules that have a specific branch or tag.
           May be used repeatedly to require several refs.

 --        End option processing.

Arguments:

 command...
          The command to run in each relevant module, along with its
          arguments.

Several --require options may be used; for example, one for a prior
release tag, another for an imminent release branch.  Note that, if a
relevant module is missing (not cloned), this script silently skips
it; you do need to have a suitable full work tree.

By way of example, here's the work-flow for producing an API change
review between $LAST and $NEXT to be tracked by Jira issue $TASK (you
can set LAST, NEXT and TASK in the environment to configure the
examples), assuming qtqa/scripts/api-review is in your PATH:

 Get qt5 up to date, but with the old .gitmodules:
 $ git checkout $NEXT && git pull --ff-only
 $ git checkout $LAST -- .gitmodules

 Check status of all relevant modules:
 $ $ME git status
 You'll need all your working trees clean before you proceed.

 Get each potentially relevant module's origin up to date:
 $ $ME git fetch origin

 Make sure each has the new branch available to check out:
 $ $ME -r origin/$NEXT \\
        git fetch origin $NEXT:$NEXT '||' git pull --ff-only

 Generate review branches:
 $ $ME -r $LAST -r $NEXT api-review-gen -t $TASK $LAST $NEXT

 Add a --amend before the -t in that to update review branches to
 reflect changes in imminent release (e.g. fixes for an earlier round
 of review) after updating $NEXT as above.

 Examine residual diff (should all be boring):
 $ $ME -r api-review-$LAST-$NEXT git diff -b -D

 Clear away residual diff (after verifying it's all boring):
 $ $ME -r api-review-$LAST-$NEXT git reset --hard HEAD

 Remove review branch where we committed nothing:
 $ $ME -r api-review-$LAST-$NEXT \\
        if git diff --quiet $LAST api-review-$LAST-$NEXT ';' \\
        then git reset --hard HEAD '&&' \\
             git checkout $NEXT '&&' \\
             git branch -D api-review-$LAST-$NEXT ';' fi
 Note the use of quoted shell metacharacters; $ME shall
 eval the command, ensuring they do their proper job.

 Examine changes (mostly interesting) to be reviewed:
 $ $ME -r api-review-$LAST-$NEXT git log -p $LAST..

 Push to gerrit:
 $ $ME -r api-review-$LAST-$NEXT git push gerrit HEAD:refs/for/$NEXT

 When $LAST has changed (only relevant when using a branch here),
 rebase review branches onto it:
 $ $ME -r api-review-$LAST-$NEXT git rebase $LAST api-review-$LAST-$NEXT
EOF
}
warn () { echo "$@" >&2; }
die () { warn "$@"; exit 1; }
banner () { echo; echo "====== $@ ======"; echo; }

# Convert each .gitmodules stanza into a single line:
modules () {
    sed -e 's/^\[submodule/\v[submodule/' <.gitmodules | tr '\n\v' '\f\n'
    echo
}

# Parse a single linearised stanza; discard if boring, else extract
# its directory name:
vet () {
    # First pass: filter out modules we don't want:
    echo "$@" | tr '\f' '\n' | while read name op value
    do
        [ "$op" = '=' ] || continue
        case "$name" in
            # In dev, status is replaced by initrepo; but it's only
            # given when true, so we can't filter on it here.
            status)
                # No BC/SC promises in preview; and we
                # don't care about obsolete or ignore:
                case "$value" in
                    essential|addon|deprecated) ;;
                    preview|obsolete|ignore) return ;;
                esac;;
            # repotools has qt = false
            qt) [ "$value" = false ] && return ;;
            # non-versioned modules aren't relevant to us:
            branch) [ "$value" = master ] && return ;;
        esac
    done
    # Because | while runs in a sub-shell, no variable assignment we
    # make there survives to be used; so we need to re-scan to extract
    # module paths:
    echo "$@" | grep -w '\(status *= *\|initrepo *= *true\)' | \
        tr '\f' '\n' | grep 'path *=' | cut -d = -f 2
}

# Re-echo a module if it has all required refs:
checkrefs () {
    for ref in $REFS
    # Use rev-parse --verify to test whether $ref exists in this repo:
    do (cd "$1" && git rev-parse -q --verify "$ref^{commit}" >/dev/null 2>&1) || return
    done
    echo "$1"
}

# List the API-relevant modules:
relevant () {
    # Select released Qt modules:
    modules | while read stanza
    do vet "$stanza"
    done | while read path
    # Only those with src/ and a sync.profile matter:
    do if [ -d "$path/src" -a -f "$path/sync.profile" ]
       # Filter on the desired refs (if any):
       then checkrefs "$path"
       fi
    done
}

[ -e .gitmodules ] || die "I must be run in the top-level (qt5) directory"
REFS=
while [ $# -gt 0 ]
do
    case "$1" in
        -u|--usage) usage; exit 0;;
        -h|--help) help; exit 0;;
        -r|--require) REFS="$REFS $2"; shift 2;;
        --) shift; break;;
        -*) usage >&2; die "Unrecognised option: $1";;
        *) break;;
    esac
done
if [ $# -eq 0 ]
then
    usage >&2
    die "You need to supply a command to run in each module !"
fi

relevant | while read dir
do (cd "$dir" && banner "$dir" && eval "$@") || warn "Failed ($?) in $dir: $@"
done
